package service

import (
	"bytes"
	"gitee.com/sunny-zhangqiang/zcache_new/cache/base"
	"gitee.com/sunny-zhangqiang/zcache_new/cache/fusion"
	"gitee.com/sunny-zhangqiang/zcache_new/comm"
	"gitee.com/sunny-zhangqiang/zcache_new/config"
	"strconv"
)

var (
	cacher                     base.Cacher
	cacheObjectMetaMagic       = []byte("ZCACHE-MAGIC-ABCD: 12345678\r\n")
	cacheObjectMetaMagicLen    = len(cacheObjectMetaMagic)
	cacheMetaChunkedName       = []byte("chunked")
	cacheMetaVersionName       = []byte("version")
	cacheMetaIdentifyName      = []byte("identify")
	cacheStatusName            = []byte("status")
	cacheMetaFragmentSizeName  = []byte("fragment-size")
	cacheContentLengthName     = []byte("content-length")
	cacheMetaWriteTimeSizeName = []byte("write-time")
)

type cacheObject struct {
	chunked       string
	version       string
	identify      string
	status        string
	fragmentSize  int64
	contentLength int64
	writeTime     int64
	headerAndBody []byte
}

// key: value, the key is lower string
func parseCacheMetaLine(obj *cacheObject, line []byte) error {
	off := 0

	lineLen := len(line)

	// skip space
	for off < lineLen {
		if line[off] != ' ' {
			break
		}
		off++
	}

	var name []byte

	for i := off; i < lineLen; i++ {
		if line[i] == ':' {
			name = line[off:i]
			off = i + 1
			break
		}
	}

	if name == nil {
		return invalidCacheMetaLineErr
	}

	// skip space
	for off < lineLen {
		if line[off] != ' ' {
			break
		}
		off++
	}

	if off > lineLen {
		return invalidCacheMetaLineErr
	}

	val := line[off:]
	valStr := comm.Bytes2String(val)

	if bytes.Equal(name, cacheMetaChunkedName) {
		obj.chunked = valStr
		return nil
	}

	if bytes.Equal(name, cacheMetaVersionName) {
		obj.version = valStr
		return nil
	}

	if bytes.Equal(name, cacheMetaIdentifyName) {
		obj.identify = valStr
	}

	if bytes.Equal(name, cacheStatusName) {
		obj.status = valStr
		return nil
	}

	if bytes.Equal(name, cacheMetaFragmentSizeName) {
		i, err := strconv.ParseInt(valStr, 10, 64)
		if err != nil || i < 0 {
			return invalidCacheFragmentSizeErr
		}
		obj.fragmentSize = i
		return nil
	}

	if bytes.Equal(name, cacheContentLengthName) {
		i, err := strconv.ParseInt(valStr, 10, 64)
		if err != nil {
			return invalidCacheContentLengthErr
		}
		obj.contentLength = i
	}

	if bytes.Equal(name, cacheMetaWriteTimeSizeName) {
		i, err := strconv.ParseInt(valStr, 10, 64)
		if err != nil || i < 0 {
			return invalidCacheWriteTimeErr
		}
		obj.writeTime = i
		return nil
	}

	return nil
}

func ParseCacheObject(obj *cacheObject, value []byte) error {
	if len(value) <= cacheObjectMetaMagicLen ||
		!bytes.Equal(cacheObjectMetaMagic, value[0:cacheObjectMetaMagicLen]) {
		return invalidCacheMetaMagicErr
	}

	obj.contentLength = -1
	value = value[cacheObjectMetaMagicLen:]

	vl := len(value)
	if vl > 4096 {
		vl = 4096
	}

	foundEmptyLine := false

	off := 0
	for i := 0; i < vl; i++ {
		if i > 0 && value[i] == '\n' && value[i-1] == '\r' {
			line := value[off : i-1]
			if len(line) == 0 {
				foundEmptyLine = true
				off = i + 1
				break
			}
			err := parseCacheMetaLine(obj, line)
			if err != nil {
				return invalidCacheMetaLineErr
			}
			off = i + 1
		}
	}

	if !foundEmptyLine {
		return invalidCacheMetaErr
	}

	if obj.status == "" {
		return invalidCacheStatusErr
	}

	if off > len(value) {
		return invalidCacheObjectErr
	}

	obj.headerAndBody = value[off:]

	return nil
}

func SerialCacheObject(obj *cacheObject) []byte {
	n := 0
	buf := make([]byte, len(obj.headerAndBody)+2048)
	n += copy(buf[n:], cacheObjectMetaMagic)
	if obj.chunked != "" {
		n += copy(buf[n:], cacheMetaChunkedName)
		n += copy(buf[n:], ": ")
		n += copy(buf[n:], obj.chunked)
		n += copy(buf[n:], "\r\n")
	}
	if obj.version != "" {
		n += copy(buf[n:], cacheMetaVersionName)
		n += copy(buf[n:], ": ")
		n += copy(buf[n:], obj.version)
		n += copy(buf[n:], "\r\n")
	}
	if obj.identify != "" {
		n += copy(buf[n:], cacheMetaIdentifyName)
		n += copy(buf[n:], ": ")
		n += copy(buf[n:], obj.identify)
		n += copy(buf[n:], "\r\n")
	}
	if obj.status != "" {
		n += copy(buf[n:], cacheStatusName)
		n += copy(buf[n:], ": ")
		n += copy(buf[n:], obj.status)
		n += copy(buf[n:], "\r\n")
	}
	n += copy(buf[n:], cacheMetaFragmentSizeName)
	n += copy(buf[n:], ": ")
	n += copy(buf[n:], strconv.FormatInt(obj.fragmentSize, 10))
	n += copy(buf[n:], "\r\n")
	n += copy(buf[n:], cacheContentLengthName)
	n += copy(buf[n:], ": ")
	n += copy(buf[n:], strconv.FormatInt(obj.contentLength, 10))
	n += copy(buf[n:], "\r\n")
	n += copy(buf[n:], cacheMetaWriteTimeSizeName)
	n += copy(buf[n:], ": ")
	n += copy(buf[n:], strconv.FormatInt(obj.writeTime, 10))
	n += copy(buf[n:], "\r\n")
	n += copy(buf[n:], "\r\n")
	n += copy(buf[n:], obj.headerAndBody)
	return buf[:n]
}

func OpenCache(createNew bool) error {
	c, err := fusion.Open(config.ServerConf.Cache, createNew)
	if err != nil {
		return err
	}

	cacher = c
	return nil
}

func CloseCache() error {
	if cacher == nil {
		return nil
	}

	return cacher.Close()
}
